# -*- coding: UTF-8 -*-
from Cookie import SimpleCookie
from pprint import pformat
import datastructures

DEFAULT_MIME_TYPE = 'text/html'

class HttpRequest(object): # on doit faire de cette classe une classe du nouveau style car certaines sous-classes dfinissent des "propriétés"
    "Une requête HTTP de base"
    def __init__(self):
        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
        self.path = ''

    def __repr__(self):
        return '<Objet HttpRequest\nGET : %s,\nPOST : %s,\nCOOKIES : %s,\nMÉTA : %s>' % \
            (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
            pformat(self.META))

    def __getitem__(self, key):
        for d in (self.POST, self.GET):
            if d.has_key(key):
                return d[key]
        raise KeyError, "'%s' introuvable, à la fois dans POST et GET" % key

    def get_full_path(self):
        return ''

def parse_file_upload(header_dict, post_data):
    "Renvoie un tuple de la forme (POST, FILES), où POST et FILES sont deux objets MultiValueDict"
    import email, email.Message
    from cgi import parse_header
    raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
    raw_message += '\r\n\r\n' + post_data
    msg = email.message_from_string(raw_message)
    POST = datastructures.MultiValueDict()
    FILES = datastructures.MultiValueDict()
    for submessage in msg.get_payload():
        if isinstance(submessage, email.Message.Message):
            name_dict = parse_header(submessage['Content-Disposition'])[1]
            # name_dict est un dictionnaire de la forme {'name': 'fichier', 'filename': 'test.txt'} pour les fichiers
            # uploadés par méthode POST; pour les autres champs POST, il prend la forme {'name': 'toto'}.
            # Nous supposons que les fichiers uploadés par méthode POST possèdent un attribut 'filename' ayant une valeur
            # correcte.
            if name_dict.has_key('filename'):
                assert type([]) != type(submessage.get_payload()), "Les messages MIME imbriqués en plusieurs parties ne sont pas pris en charge."
                if not name_dict['filename'].strip():
                    continue
                # IE envoie le chemin d'accès complet du fichier comme nom de fichier; on veut seulement garder le nom du
                # fichier. (On ne peut pas utiliser pour cela os.path.basename car cette fonction accepte des chemins
                # d'accès Unix.)
                filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
                FILES.appendlist(name_dict['name'], {
                    'filename': filename,
                    'content-type': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
                    'content': submessage.get_payload(),
                })
            else:
                POST.appendlist(name_dict['name'], submessage.get_payload())
    return POST, FILES

class QueryDict(datastructures.MultiValueDict):
    """Un objet MultiValueDict spécialisé qui accepte en argument une query string lors de son initialisation.
    Cet objet est immuable sauf si vous créez une copie."""
    def __init__(self, query_string):
        try:
            from mod_python.util import parse_qsl
        except ImportError:
            from cgi import parse_qsl
        if not query_string:
            self.data = {}
            self._keys = []
        else:
            self.data = {}
            self._keys = []
            for name, value in parse_qsl(query_string, True): # keep_blank_values=True
                if name in self.data:
                    self.data[name].append(value)
                else:
                    self.data[name] = [value]
                if name not in self._keys:
                    self._keys.append(name)
        self._mutable = False

    def __setitem__(self, key, value):
        if not self._mutable:
            raise AttributeError, "Cette instance de la classe QueryDict est immuable."
        else:
            self.data[key] = [value]
            if not key in self._keys:
                self._keys.append(key)

    def setlist(self, key, list_):
        if not self._mutable:
            raise AttributeError, "Cette instance de la classe QueryDict est immuable."
        else:
            self.data[key] = list_
            if not key in self._keys:
                self._keys.append(key)

    def copy(self):
        "Renvoie une copie muable de cet objet."
        cp = datastructures.MultiValueDict.copy(self)
        cp._mutable = True
        return cp

    def assert_synchronized(self):
        assert(len(self._keys) == len(self.data.keys())), \
            "La structure de données QueryDict n'est pas synchronisée : %s %s" % (str(self._keys), str(self.data))

    def items(self):
        "On respecte l'ordre offert par self._keys."
        self.assert_synchronized()
        items = []
        for key in self._keys:
            if key in self.data:
                items.append((key, self.data[key][0]))
        return items

    def keys(self):
        self.assert_synchronized()
        return self._keys

def parse_cookie(cookie):
    if cookie == '':
        return {}
    c = SimpleCookie()
    c.load(cookie)
    cookiedict = {}
    for key in c.keys():
        cookiedict[key] = c.get(key).value
    return cookiedict

class HttpResponse:
    "Une réponse HTTP de base, avec contenu et en-têtes (sous forme de dictionnaire)."
    def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
        self.content = content
        self.headers = {'Content-Type':mimetype}
        self.cookies = SimpleCookie()
        self.status_code = 200

    def __str__(self):
        "Le message HTTP complet, y compris les en-têtes."
        return '\n'.join(['%s: %s' % (key, value)
            for key, value in self.headers.items()]) \
            + '\n\n' + self.content

    def __setitem__(self, header, value):
        self.headers[header] = value

    def __delitem__(self, header):
        try:
            del self.headers[header]
        except KeyError:
            pass

    def __getitem__(self, header):
        return self.headers[header]

    def has_header(self, header):
        "Contrôle la présence d'un en-tête (test insensible à la casse)."
        header = header.lower()
        for key in self.headers.keys():
            if key.lower() == header:
                return True
        return False

    def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=None):
        self.cookies[key] = value
        for var in ('max_age', 'path', 'domain', 'secure'):
            val = locals()[var]
            if val is not None:
                self.cookies[key][var.replace('_', '-')] = val

    def get_content_as_string(self, encoding):
        """
        Renvoie le contenu de la réponse sous forme de chaîne; si le contenu est un objet Unicode, on utilise
        le codage de caractères spécifié.
        """
        if isinstance(self.content, unicode):
            return self.content.encode(encoding)
        return self.content

    # Les méthodes restantes implémentent partiellement l'interface Python des objets fichier.
    # Voir http://docs.python.org/lib/bltin-file-objects.html
    def write(self, content):
        self.content += content

    def flush(self):
        pass

    def tell(self):
        return len(self.content)

class HttpResponseRedirect(HttpResponse):
    def __init__(self, redirect_to):
        HttpResponse.__init__(self)
        self['Location'] = redirect_to
        self.status_code = 302

class HttpResponseNotModified(HttpResponse):
    def __init__(self):
        HttpResponse.__init__(self)
        self.status_code = 304

class HttpResponseNotFound(HttpResponse):
    def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
        HttpResponse.__init__(self, content, mimetype)
        self.status_code = 404

class HttpResponseForbidden(HttpResponse):
    def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
        HttpResponse.__init__(self, content, mimetype)
        self.status_code = 403

class HttpResponseGone(HttpResponse):
    def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
        HttpResponse.__init__(self, content, mimetype)
        self.status_code = 410

class HttpResponseServerError(HttpResponse):
    def __init__(self, content='', mimetype=DEFAULT_MIME_TYPE):
        HttpResponse.__init__(self, content, mimetype)
        self.status_code = 500